import uuid
from collections import OrderedDict

import sqlalchemy as sa
from sqlalchemy import Column, Integer, ForeignKey, Table, create_engine
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.orm import relationship, backref
from sqlalchemy.orm import sessionmaker
from sqlalchemy.sql.expression import FunctionElement

from bopress import settings

__author__ = 'i@tinyms.com'

@declared_attr
def __tablename__(*args):
    self = args[0]
    table_name = self.__name__.lower()
    return "%s%s" % (settings.TABLE_NAME_PREFIX, table_name)


Entity = declarative_base()
Entity.__tablename__ = __tablename__


def primary_key():
    """
    生成UUID表主键
    :return:
    """
    return str(uuid.uuid1())


class SessionFactory(object):
    __engine__ = None

    @staticmethod
    def instance():
        if SessionFactory.__engine__:
            return SessionFactory.__engine__
        SessionFactory.__engine__ = create_engine(settings.DB_CONNECT_URI, echo=settings.DEBUG, encoding="utf-8")
        return SessionFactory.__engine__

    @staticmethod
    def session():
        return sessionmaker(bind=SessionFactory.instance())()

    @staticmethod
    def connect():
        return SessionFactory.instance().connect()

    @staticmethod
    def create_tables():
        Entity.metadata.create_all(SessionFactory.instance())


def get_primary_keys(entity_cls):
    cols = sa.inspect(entity_cls).columns
    return OrderedDict(
        (
            (key, column) for key, column in cols.items()
            if column.primary_key
        )
    )


def get_ref_column(entity_cls):
    cols = get_primary_keys(entity_cls)
    if len(cols) == 1:
        for c in cols:
            return cols[c]
    return None


# def one_to_many(target_cls, fk_field_name, ref_field_name="id", ref_field_data_type=String(40), ondelete="CASCADE",
#                 onupdate="CASCADE"):
#     """
#     注意: 子实体类必须写在父实体类前
#     一对多，相对应关联的子实体类则是多对一
#     class Parent(Base):
#         __tablename__ = 'parent'
#         id = Column(Integer, primary_key=True)
#         children = relationship("Child")
#
#     class Child(Base):
#         __tablename__ = 'child'
#         id = Column(Integer, primary_key=True)
#         parent_id = Column(Integer, ForeignKey('parent.id'))
#     父访问子: parent_instance.children_set，即子实体类名小写+`_set`
#     子访问父: child_instance.parent， 父实体类名小写
#     :param fk_field_name:
#     :param onupdate: 级联更新
#     :param ondelete: 级联删
#     :param target_cls: 子实体类
#     :param ref_field_name: 关联实体类外键引用的字段名称
#     :param ref_field_data_type: 关联实体类外键引用的字段数据类型
#     :return:
#     """
#
#     def ref_table(cls):
#         self_table_name = "%s%s" % (settings.TABLE_NAME_PREFIX, cls.__name__.lower())
#         setattr(cls, '{0}_set'.format(target_cls.__name__.lower()),
#                 relationship(target_cls.__name__, backref=self_table_name))
#         setattr(target_cls, fk_field_name, Column(ref_field_data_type,
#                                                   ForeignKey('%s.%s' % (self_table_name, ref_field_name),
#                                                              ondelete=ondelete, onupdate=onupdate),
#                                                   nullable=True))
#         return cls
#
#     return ref_table


def many_to_one(target_cls, ondelete="CASCADE", onupdate="CASCADE"):
    """
    多对一，一对多共用这种形式
    一旦映射成功，one的一方将自动拥有many一方集合变量名`*_set`
    :param onupdate: 级联更新 CASCADE, DELETE and RESTRICT
    :param ondelete: 级联删除 CASCADE, DELETE and RESTRICT
    :param target_cls: 父实体类
    :return:
    """

    def ref_table(cls):
        pk = get_ref_column(target_cls)
        if pk is None:
            return cls
        target_table_name = "%s%s" % (settings.TABLE_NAME_PREFIX, target_cls.__name__.lower())
        # parent_id=>parent.id
        setattr(cls, pk.key, Column(pk.type,
                                    ForeignKey('%s.%s' % (target_table_name, pk.name),
                                               ondelete=ondelete, onupdate=onupdate),
                                    nullable=True))
        # parent, children_set
        setattr(cls, '{0}'.format(target_cls.__name__.lower()),
                relationship(target_cls.__name__, backref="{0}_set".format(cls.__name__.lower())))

        return cls

    return ref_table


def one_to_one(target_entity_cls, ondelete="CASCADE", onupdate="CASCADE"):
    """

    :param target_entity_cls: 父实体类
    :param ondelete: 级联更新 CASCADE, DELETE and RESTRICT
    :param onupdate: 级联删除 CASCADE, DELETE and RESTRICT
    :return:
    """

    def ref_table(cls):
        pk = get_ref_column(target_entity_cls)
        if pk is None:
            return cls
        target_table_name = "%s%s" % (settings.TABLE_NAME_PREFIX, target_entity_cls.__name__.lower())
        setattr(cls, pk.key, Column(pk.type,
                                    ForeignKey('%s.%s' % (target_table_name, pk.name),
                                               ondelete=ondelete, onupdate=onupdate),
                                    nullable=True))
        # parent, children, no children_set
        setattr(cls, '{0}'.format(target_entity_cls.__name__.lower()),
                relationship(target_entity_cls.__name__, backref=backref(cls.__name__.lower(), uselist=False)))

        return cls

    return ref_table


def many_to_many(target_entity_cls, ondelete="CASCADE", onupdate="CASCADE", lazy='dynamic'):
    """
    多对多，装饰到有关联关系的任意实体之上
    一旦映射成功，彼此皆可获取对方`表名_set`集合变量
    :return:
    """

    def ref_table(cls):
        cls_pk = get_ref_column(cls)
        if cls_pk is None:
            return cls
        target_cls_pk = get_ref_column(target_entity_cls)
        if target_cls_pk is None:
            return cls
        target_name = target_entity_cls.__name__.lower()
        self_name = cls.__name__.lower()
        association_table = Table(
            '{0}{1}_{2}_relationships'.format(settings.TABLE_NAME_PREFIX, self_name, target_name),
            Entity.metadata,
            Column(target_cls_pk.pk, target_cls_pk.type,
                   ForeignKey('{0}{1}.{2}'.format(settings.TABLE_NAME_PREFIX, target_name, target_cls_pk.name),
                              ondelete=ondelete, onupdate=onupdate)),
            Column(cls_pk.pk, cls_pk.type,
                   ForeignKey('{0}{1}.{2}'.format(settings.TABLE_NAME_PREFIX, self_name, cls_pk.name),
                              ondelete=ondelete, onupdate=onupdate))
        )
        # parent_set, children_set
        setattr(cls, target_name,
                relationship("%s_set" % target_name, secondary=association_table,
                             backref=backref(cls.__name__.lower() + '_set', lazy=lazy)))
        return cls

    return ref_table


# 特别的数据库处理函数

# 计算两个日期之间以分钟为单位的差值，返回整数
class MinuteDiff(FunctionElement):
    type = Integer()
    name = "minute_diff"


@compiles(MinuteDiff, 'mssql')
def _mssql_minute_diff(element, compiler, **kw):
    return "DATEDIFF(MINUTE, %s, %s)" % (compiler.process(element.clauses.clauses[0]),
                                         compiler.process(element.clauses.clauses[1]))


@compiles(MinuteDiff, 'mysql')
def _mysql_minute_diff(element, compiler, **kw):
    return "TIMESTAMPDIFF(MINUTE, %s, %s)" % (compiler.process(element.clauses.clauses[0]),
                                              compiler.process(element.clauses.clauses[1]))


@compiles(MinuteDiff, 'sqlite')
def _mysql_minute_diff(element, compiler, **kw):
    return "(julianday(%s) - julianday(%s))*24*60" % (compiler.process(element.clauses.clauses[0]),
                                                      compiler.process(element.clauses.clauses[1]))
